#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "parser.h"

/* define default callbacks */
static void 
esi_parser_default_start_cb( const void *data,
                             const char *name_start,
                             size_t name_length,
                             ESIAttribute *attributes,
                             void *user_data )
{
}
static void 
esi_parser_default_end_cb( const void *data,
                           const char *name_start,
                           size_t name_length,
                           void *user_data )
{
}
static void 
esi_parser_default_output_cp(const void *data,
                             size_t length,
                             void *user_data)
{
}
#ifdef DEBUG
static void debug_string( const char *msg, const char *str, size_t len )
{
  char *pstr = esi_strndup( str, len );
  printf( "%s :'%s'\n", msg, pstr );
  free( pstr );
}
#endif

static void esi_parser_echo_char( ESIParser *parser, char ch )
{
  parser->output_handler( (void*)&ch, 1, parser->user_data );
}

static void esi_parser_echo_buffer( ESIParser *parser )
{
//  debug_string( "echobuffer", parser->echobuffer, parser->echobuffer_index+1 );
  parser->output_handler( parser->echobuffer, parser->echobuffer_index+1, parser->user_data );
}

static void esi_parser_echobuffer_clear( ESIParser *parser )
{
  parser->echobuffer_index = -1;
}

static void esi_parser_concat_to_echobuffer( ESIParser *parser, char ch )
{
  parser->echobuffer_index++;

  if( parser->echobuffer_allocated <= parser->echobuffer_index ){
    // double the echobuffer size
    parser->echobuffer_allocated *= 2;
    parser->echobuffer = (char*)realloc( parser->echobuffer, parser->echobuffer_allocated );
  }
  parser->echobuffer[parser->echobuffer_index] = ch;
//  debug_string( "echo buffer", parser->echobuffer, parser->echobuffer_index+1 );
}

static void ltrim_pointer( const char **ptr, const char *bounds, size_t *len )
{
  // remove any spaces or = at the before the value
  while( isspace( **ptr ) ||
         **ptr == '=' ||
         **ptr == '"' ||
         **ptr == '<' ||
         **ptr == '\'' &&
         *len > 0 &&
         (*ptr != bounds) ){
    (*ptr)++;
    (*len)--;
  }
}

%%{
  machine esi;

  action begin {
    parser->mark = p;
    //debug_string( "begin", p, 1 );
  }
  action finish {
//    printf( "finish\n" );
  }

  # record the position of the start tag
  action see_start_tag {
    parser->tag_text = parser->mark+1;
    parser->tag_text_length = p - (parser->mark+1);
//    debug_string( "have esi start", parser->tag_text, parser->tag_text_length );
    parser->mark = p;
    //debug_string( "see_start_tag", p, 1 );
  }

  action see_end_tag {
    //debug_string( "see_end_tag: before parsed esi tag", parser->tag_text, parser->tag_text_length );
    ltrim_pointer( &(parser->tag_text), p, &(parser->tag_text_length) );
    //debug_string( "see_end_tag: after parsed esi tag", parser->tag_text, parser->tag_text_length );
    parser->start_tag_handler( data, parser->tag_text, parser->tag_text_length, parser->attributes, parser->user_data );
    //debug_string( "see_end_tag: after start parsed esi tag", parser->tag_text, parser->tag_text_length );
    parser->end_tag_handler( data, parser->tag_text, parser->tag_text_length, parser->user_data );
    //debug_string( "see_end_tag: after end parsed esi tag", parser->tag_text, parser->tag_text_length );
    parser->tag_text = NULL;
    parser->tag_text_length = 0;
    parser->mark = p;
    //debug_string( "see_end_tag", p, 1 );
  }
  
  action see_block_start_with_attributes {
//    debug_string( "parsed esi tag", parser->tag_text, parser->tag_text_length );
    ltrim_pointer( &(parser->tag_text), p, &(parser->tag_text_length) );
    parser->start_tag_handler( data, parser->tag_text, parser->tag_text_length, parser->attributes, parser->user_data );
    parser->tag_text = NULL;
    parser->tag_text_length = 0;
  }

  action see_attribute_key {
    parser->attr_key = parser->mark;
    parser->attr_key_length = p - parser->mark;
    parser->mark = p;
    ltrim_pointer( &(parser->attr_key), p, &(parser->attr_key_length) );
//    debug_string( "\tattribute key", parser->attr_key, parser->attr_key_length );
    //debug_string( "see_attribute_key", p, 1 );
  }

  action see_attribute_value {
    ESIAttribute *attr;
    parser->attr_value = parser->mark;
    parser->attr_value_length = p - parser->mark;
    parser->mark = p;

    ltrim_pointer( &(parser->attr_value), p, &(parser->attr_value_length) );

    // allocate a new attribute
    attr = esi_attribute_new( parser->attr_key, parser->attr_key_length, 
                              parser->attr_value, parser->attr_value_length );
    if( parser->attributes ){
      parser->last->next = attr;
      parser->last = attr;
    }
    else{
      parser->last = parser->attributes = attr;
    }
//    debug_string( "\tattribute value", parser->attr_value, parser->attr_value_length );
    //debug_string( "see_attribute_value", p, 1 );
  }

  action block_start_tag {
    parser->tag_text = parser->mark;
    parser->tag_text_length = p - parser->mark;
    parser->mark = p;
    ltrim_pointer( &(parser->tag_text), p, &(parser->tag_text_length) );
    parser->start_tag_handler( data, parser->tag_text, parser->tag_text_length, NULL, parser->user_data );
    //debug_string( "block_start_tag", p, 1 );
  }

  action block_end_tag {
    parser->tag_text = parser->mark+2;
    parser->tag_text_length = p - (parser->mark+2);
    //printf( "mark: [%s], p: [%s], length: %d\n", parser->mark, p, parser->tag_text_length );
    parser->mark = p;
    //debug_string( "block_end_tag: before parsed esi tag", parser->tag_text, parser->tag_text_length );
    ltrim_pointer( &(parser->tag_text), p, &(parser->tag_text_length) );
    //debug_string( "block_end_tag: after parsed esi tag", parser->tag_text, parser->tag_text_length );
    parser->end_tag_handler( data, parser->tag_text, parser->tag_text_length, parser->user_data );
  }

  action echo {
    switch( cs ){
    case 0:
      if( parser->prev_state != 12 && parser->prev_state != 7 ){
        if( parser->echobuffer && (parser->prev_state != (esi_en_main +1)) && parser->prev_state != 60 ){
          // stream echobuffer
          esi_parser_echo_buffer( parser );
        }
//    printf( "[%d: %c]", cs, *p );
        // stream the current character
        esi_parser_echo_char( parser, *p );
      }
      // clear the echobuffer
      esi_parser_echobuffer_clear( parser );
      break;
    default:
      // append characters to echobuffer
      esi_parser_concat_to_echobuffer( parser, *p );
    }
    parser->prev_state = cs;
  }

  include esi_common_parser "common.rl";
}%%

%%write data;

char *esi_strndup( const char *str, size_t len )
{
  char *s = (char*)malloc(sizeof(char)*(len+1));
  memcpy( s, str, len );
  s[len] = '\0';
  return s;
}

ESIAttribute *esi_attribute_new( const char *name, size_t name_length, const char *value, size_t value_length )
{
  ESIAttribute *attr = (ESIAttribute*)malloc(sizeof(ESIAttribute));
  attr->name = esi_strndup(name, name_length);
  attr->value = esi_strndup(value, value_length);
  attr->next = NULL;
  return attr;
}

ESIAttribute *esi_attribute_copy( ESIAttribute *attribute )
{
  ESIAttribute *head, *nattr;
  if( !attribute ){ return NULL; }

  // copy the first attribute
  nattr = esi_attribute_new( attribute->name, strlen( attribute->name ),
                             attribute->value, strlen( attribute->value ) );
  // save a pointer for return
  head = nattr;
  // copy next attributes
  attribute = attribute->next;
  while( attribute ) {
    // set the next attribute
    nattr->next = esi_attribute_new( attribute->name, strlen( attribute->name ),
                                     attribute->value, strlen( attribute->value ) );
    // next attribute
    nattr = nattr->next;
    attribute = attribute->next;
  }
  return head;
}

void esi_attribute_free( ESIAttribute *attribute )
{
  ESIAttribute *ptr;
  while( attribute ){
    free( attribute->name );
    free( attribute->value );
    ptr = attribute->next;
    free( attribute );
    attribute = ptr;
  }
}

ESIParser *esi_parser_new()
{
  ESIParser *parser = (ESIParser*)malloc(sizeof(ESIParser));
  parser->cs = esi_start;
  parser->mark = NULL;
  parser->overflow_data_size = 0;
  parser->overflow_data = NULL;

  /* allocate 1024 bytes for the echobuffer */
  parser->echobuffer_allocated = 1024;
  parser->echobuffer_index = -1;
  parser->echobuffer = (char*)malloc(sizeof(char)*parser->echobuffer_allocated);

  parser->attributes = NULL;
  parser->last = NULL;

  parser->start_tag_handler = esi_parser_default_start_cb;
  parser->end_tag_handler = esi_parser_default_end_cb;
  parser->output_handler = esi_parser_default_output_cp;

  return parser;
}
void esi_parser_free( ESIParser *parser )
{
  if( parser->overflow_data ){ free( parser->overflow_data ); }

  free( parser->echobuffer );
  esi_attribute_free( parser->attributes );

  free( parser );
}

void esi_parser_output_handler( ESIParser *parser, output_cb output_handler )
{
  parser->output_handler = output_handler;
}

int esi_parser_init( ESIParser *parser )
{
  int cs;
  %% write init;
  parser->prev_state = parser->cs = cs;
  return 0;
}

static int compute_offset( const char *mark, const char *data )
{
  if( mark ){
    return mark - data;
  }
  return -1;
}

int esi_parser_execute( ESIParser *parser, const char *data, size_t length )
{
  int cs = parser->cs;
  const char *p = data;
  const char *pe = data + length;

  if( length == 0 ){ return cs; }

  if( parser->overflow_data && parser->overflow_data_size > 0 ){

    // recompute mark, tag_text, attr_key, and attr_value since they all exist within overflow_data
    int mark_offset = compute_offset( parser->mark, parser->overflow_data );
    int tag_text_offset = compute_offset( parser->tag_text, parser->overflow_data );
    int attr_key_offset = compute_offset( parser->attr_key, parser->overflow_data );
    int attr_value_offset = compute_offset( parser->attr_value, parser->overflow_data );

//    debug_string("grow overflow buffer", parser->overflow_data, parser->overflow_data_size );

    parser->overflow_data = (char*)realloc( parser->overflow_data, sizeof(char)*(parser->overflow_data_size+length) );
    memcpy( parser->overflow_data+parser->overflow_data_size, data, length );

    p = parser->overflow_data + parser->overflow_data_size;
    
    // in our new memory space mark will now be
    parser->mark = ( mark_offset >= 0 ) ? parser->overflow_data + mark_offset : NULL;
    parser->tag_text = ( tag_text_offset >= 0 ) ? parser->overflow_data + tag_text_offset : NULL;
    parser->attr_key = ( attr_key_offset >= 0 ) ? parser->overflow_data + attr_key_offset : NULL;
    parser->attr_value = ( attr_value_offset >= 0 ) ? parser->overflow_data + attr_value_offset : NULL;
 
    data = parser->overflow_data;
    parser->overflow_data_size = length = length + parser->overflow_data_size;
    pe = data + length;

//    debug_string( "overflow", parser->overflow_data, parser->overflow_data_size );
  }

  if( !parser->mark ){
    parser->mark = p;
  }

//  printf( "cs: %d, ", cs );
//  debug_string( "data", data, length );

  %% write exec;

  parser->cs = cs;

  if( cs != esi_start && cs != 0 ){

    if( !parser->overflow_data ){
      // recompute mark, tag_text, attr_key, and attr_value since they all exist within overflow_data
      int mark_offset = compute_offset( parser->mark, data );
      int tag_text_offset = compute_offset( parser->tag_text, data );
      int attr_key_offset = compute_offset( parser->attr_key, data );
      int attr_value_offset = compute_offset( parser->attr_value, data );
      //debug_string( "mark before move", parser->mark, 1 );

      parser->overflow_data = (char*)malloc( sizeof( char ) * length );
      memcpy( parser->overflow_data, data, length );
      parser->overflow_data_size = length;

      // in our new memory space mark will now be
      parser->mark = ( mark_offset >= 0 ) ? parser->overflow_data + mark_offset : NULL;
      parser->tag_text = ( tag_text_offset >= 0 ) ? parser->overflow_data + tag_text_offset : NULL;
      parser->attr_key = ( attr_key_offset >= 0 ) ? parser->overflow_data + attr_key_offset : NULL;
      parser->attr_value = ( attr_value_offset >= 0 ) ? parser->overflow_data + attr_value_offset : NULL;
      //if( parser->mark ){ debug_string( "mark after  move", parser->mark, 1 ); } else { printf( "mark is now empty\n" ); }
    }

  }else if( parser->overflow_data ){
    free( parser->overflow_data );
    parser->overflow_data = NULL;
    parser->overflow_data_size = 0;
  }

  return cs;
}
int esi_parser_finish( ESIParser *parser )
{
  %% write eof;
  return 0;
}

void esi_parser_start_tag_handler( ESIParser *parser, start_tag_cb callback )
{
  parser->start_tag_handler = callback;
}

void esi_parser_end_tag_handler( ESIParser *parser, end_tag_cb callback )
{
  parser->end_tag_handler = callback;
}
